/**
 *  @file QuotedPrintable.cpp
 */
#include "QuotedPrintable.h"
#include "Convert.h"
#include "../common/CppMemPool.h"

namespace cppflib
{

namespace convert
{

namespace qp
{

//! maximum number of bytes of a line  (not including "\r\n")
#define MAXLINESIZE 76

/**
 *  Encode data to quoted printable or calculate buffer needed.
 *
 *  @param[in] srcBuf -- data buffer to encode
 *  @param[in] srcLen -- length of srcBuf in byte
 *  @param[out] outBuf -- buffer that will hold the encoded data 
 *                        or NULL if to calculate buffer size needed
 *  @param[out] outLen -- buffer size of outBuf in bytes
 *  @param [in] isMsgHdr -- true: encoding message header
 *                          false: encoding message body
 *
 *  @return byte needed for encoding
 */
PRIVATE int __EncodeBuffer(const u8_t * srcBuf, const int srcLen, u8_t * outBuf, int outLen, bool isMsgHdr)
{
   const u8_t * pEnd = srcBuf + srcLen;
   u8_t * pOut = outBuf;
   u8_t * pOutEnd = NULL;
   u8_t tData = 0;
   int byteNeed = 0;  // total byte need
   int lnCharCnt = 0; // line character count
  

   pOutEnd = pOut ? pOut + outLen : NULL;

   while (srcBuf < pEnd) {
      tData = *srcBuf++;

      if (isMsgHdr) {
         // exception: '\t', ' ', '?', '_'
         if (tData == 0x09 || tData == 0x20 || tData == 0x3f || tData == 0x5f)
            goto msgHdrExcept;
      }

      //
      // printable chars except '='
      if ((tData >= 0x21 && tData <= 0x3c) || (tData >= 0x3e && tData <= 0x7e)) {
   
         if (lnCharCnt + 1 >= MAXLINESIZE) {
            // soft line break -- '=','\r','\n'
            byteNeed += 3;
            lnCharCnt = 0;
            if (pOut && pOut + 2 < pOutEnd) {
               *pOut++ = '='; *pOut++ = '\r'; *pOut++ = '\n';
            }
         } 
        
         // direct assignment
         ++byteNeed;
         ++lnCharCnt;
         if (pOut && pOut < pOutEnd) {
            *pOut++ = tData;
         }
      }
      //
      // TAB and SPACE
      else if (tData == 0x09 || tData == 0x20) {
    
         //
         // walk through the buffer of white spaces until 
         // end of buffer or non-white spaces
         //    
         do {                     
     
            // check if output buffer reaches the end
            if (lnCharCnt + 1 >= MAXLINESIZE) {
               // soft line break -- '=','\r','\n'
               byteNeed += 3;
               lnCharCnt = 0;
               if (pOut && pOut + 2 < pOutEnd) {
                  *pOut++ = '='; *pOut++ = '\r'; *pOut++ = '\n';
               }
            }

            // direct assignment 
            ++byteNeed;
            ++lnCharCnt;
            if (pOut && pOut < pOutEnd) {
               *pOut++ = tData;
            }

            tData = *srcBuf;
            if (tData != 0x09 && tData != 0x20)
               break;

            ++srcBuf;
                      
         } while (srcBuf < pEnd);
                    
      }
      //
      // other binary non-printable chars; assign the "=XX" format
      else {

msgHdrExcept:

         do {
            //
            // hard line break
            if (tData == 0x0d) {
               if (srcBuf < pEnd && *srcBuf == 0x0a) {
                  byteNeed += 2;
                  lnCharCnt = 0;
                  ++srcBuf;
                  if (pOut && pOut + 1 < pOutEnd) {
                     *pOut++ = '\r'; *pOut++ = '\n';
                  }
                  break;  // don't do following
               }
            }
        
            if (lnCharCnt + 3 >= MAXLINESIZE) {
               // soft line break -- '=','\r','\n'
               byteNeed += 3;
               lnCharCnt = 0;
               if (pOut && pOut + 2 < pOutEnd) {
                  *pOut++ = '='; *pOut++ = '\r'; *pOut++ = '\n';
               }
            }

            byteNeed += 3;
            lnCharCnt += 3;
            if (pOut && pOut + 2 < pOutEnd) {
               *pOut++ = '='; 
               *pOut++ = static_cast<u8_t>(Hex2Char(tData >> 4));
               *pOut++ = static_cast<u8_t>(Hex2Char(tData & 0x0f));
            }

         } while(false);
        
      }
   
   } // end of while 
  
  
   return byteNeed;

}

/**
 *  Decode data from quoted printable or request buffer needed.
 *  
 *  @param[in] srcBuf -- data buffer to decode
 *  @param[in] srcLen -- length of srcBuf in byte
 *  @param[out] outBuf -- buffer that will hold the decoded data 
 *                        or NULL if to request buffer size needed.
 *  @param[out] outLen -- buffer size of outBuf in bytes
 *  @param [in] isMsgHdr -- true: decoding message header
 *                          false: decoding message body
 *
 *  @return byteNeed
 */
PRIVATE int __DecodeBuffer(const u8_t * srcBuf, const int srcLen, u8_t * outBuf, int outLen, bool isMsgHdr)
{
   const u8_t * pEnd = srcBuf + srcLen;  
   u8_t * pOut = outBuf;
   u8_t * pOutEnd = NULL; 
   u8_t tData = 0;
   int byteNeed = 0;  ///< total byte need

   pOutEnd = pOut ? pOut + outLen : NULL;
   
   while (srcBuf < pEnd) {
      tData = *srcBuf++;

      //
      // printable chars, TAB or SPACE, except '='
      if (tData == 0x09 || tData == 0x20 || 
          (tData >= 0x21 && tData <= 0x3c) || 
          (tData >= 0x3e && tData <= 0x7e)) {

         // convert underscore to space
         if (isMsgHdr && tData == 0x5f)
            tData = 0x20;

         // direct assignment
         ++byteNeed;
         if (pOut && pOut < pOutEnd)
            *pOut++ = tData;
      }      
      // 
      // '='
      else if (tData == 0x3d) {
         if (srcBuf + 1 < pEnd) {
            tData = *srcBuf++;
            // soft line break
            if (tData == 0x0d && *srcBuf == 0x0a) {
               // nothing to do
            }
            // binary coded data
            else {               
               ++byteNeed;

               if (pOut && pOut < pOutEnd) {
                  tData = Char2Hex(static_cast<char>(tData)) << 4;
                  tData |= Char2Hex(static_cast<char>(*srcBuf));
                  *pOut++ = tData;
               }
            }

            ++srcBuf;
         }
      }
      //
      // non-printable chars 
      else {

          if (tData == 0x0d) {
             // hard line break
             if (srcBuf < pEnd && *srcBuf == 0x0a) {
                byteNeed += 2;
                ++srcBuf;

                if (pOut && pOut + 1 < pOutEnd) {
                   *pOut++ = '\r'; *pOut++ = '\n';
                }
                   
             }
          }

          // other characters should not appear, just ignore them.
      }

   } // end of while
  
   return byteNeed;
}

/**
 *  Get buffer need for encoding
 *
 *  @param[in] srcBuf -- data buffer to encode
 *  @param[in] srcLen -- length of srcBuf in byte
 *  @param [in] isMsgHdr -- true: encoding message header
 *                          false: encoding message body
 */
_DLLAPI int GetEncodeBufferNeed(const u8_t * srcBuf, const int srcLen, const bool isMsgHdr)
{
   return __EncodeBuffer(srcBuf, srcLen, NULL, 0, isMsgHdr);
}

/**
 *  Get buffer need for decoding
 *
 *  @param[in] srcBuf -- data buffer to decode
 *  @param[in] srcLen -- length of srcBuf in byte
 *  @param [in] isMsgHdr -- true: decoding message header
 *                          false: decoding message body
 */
_DLLAPI int GetDecodeBufferNeed(const u8_t * srcBuf, const int srcLen, const bool isMsgHdr)
{
   return __DecodeBuffer(srcBuf, srcLen, NULL, 0, isMsgHdr);
}

/**
 *  An interface to encode data into quoted-printable
 *  
 *  @param[in] srcBuf -- data buffer to encode
 *  @param[in] srcLen -- length of srcBuf in byte
 *  @param[in,out] outBuf -- buffer that will hold the encoded data.
 *
 *                        If the dereferenced pointer is non-NULL, it should point to a
 *                        buffer big enough to hold the encoded data.
 *                        Call GetEncodeBufferNeed() to determine buffer size needed.
 *                        Otherwise, a new buffer is allocated and is added to current auto release pool.
 *
 *  @param[in,out] outLen -- if outBuf is non-null, should contain size of outBuf, otherwise, it will
 *                        be filled with number of bytes encoded upon return
 *  @param [in] isMsgHdr -- true: encoding message header
 *                          false: encoding message body
 *
 *  @return number of characters encoded in outBuf
 */
_DLLAPI int Encode(const u8_t * srcBuf, const int srcLen, u8_t ** outBuf, int * outLen, const bool isMsgHdr)
{
   if (*outBuf) {
      return __EncodeBuffer(srcBuf, srcLen, *outBuf, *outLen, isMsgHdr);
   }
   else {
      *outLen = GetEncodeBufferNeed(srcBuf, srcLen, isMsgHdr);
      if (*outLen > 0) {
         *outBuf = new (cppmempool::NEWPTR) u8_t[*outLen];
         if (*outBuf)
            return __EncodeBuffer(srcBuf, srcLen, *outBuf, *outLen, isMsgHdr);
      }
   }

   return 0;
}

/**
 *  An interface to decode data from quoted-printable
 *  
 *  @param[in] srcBuf -- data buffer to decode
 *  @param[in] srcLen -- length of srcBuf in byte
 *  @param[in,out] outBuf -- buffer that will hold the decoded data.
 *
 *                        If the dereferenced pointer is non-NULL, it should point to a
 *                        buffer big enough to hold the decoded data.
 *                        Call GetDecodeBufferNeed() to determine buffer size needed.
 *                        Otherwise, a new buffer is allocated and is added to current auto release pool.
 *
 *  @param[in,out] outLen -- if outBuf is non-null, should contain size of outBuf, otherwise, it will
 *                        be filled with number of bytes decoded upon return
 *  @param [in] isMsgHdr -- true: decoding message header
 *                          false: decoding message body
 *
 *  @return number of characters decoded in outBuf
 */
_DLLAPI int Decode(const u8_t * srcBuf, const int srcLen, u8_t ** outBuf, int * outLen, const bool isMsgHdr)
{
   if (*outBuf) {
      return __DecodeBuffer(srcBuf, srcLen, *outBuf, *outLen, isMsgHdr);
   }
   else {
      *outLen = GetDecodeBufferNeed(srcBuf, srcLen, isMsgHdr);
      if (*outLen > 0) {
         *outBuf = new (cppmempool::NEWPTR) u8_t[*outLen];
         if (*outBuf)
            return __DecodeBuffer(srcBuf, srcLen, *outBuf, *outLen, isMsgHdr);
      }
   }

   return 0;
}



}  // end of qp

}  // end of convert

}  // end of cppflib


